<template>
  <div id="app">
    <div class="container">
      <header class="app-header">
        <h1 class="app-title">插件门户</h1>
        <div class="header-actions">
          <div class="search-box" v-if="currentView === 'list'">
            <input 
              type="text" 
              v-model="searchQuery" 
              placeholder="搜索插件..."
              class="search-input"
            >
            <button @click="clearSearch" class="clear-button" v-if="searchQuery">
              <span class="icon">✕</span>
            </button>
          </div>
        </div>
      </header>
      
      <nav class="navbar">
        <button @click="listPlugins" :class="{ active: currentView === 'list' }" class="nav-button">
          <span class="icon">📋</span>
          插件列表
        </button>
        <button @click="showUploadForm" :class="{ active: currentView === 'upload' }" class="nav-button">
          <span class="icon">📤</span>
          上传插件
        </button>
      </nav>
      
      <div class="content">
        <!-- 上传插件表单 -->
        <div v-if="currentView === 'upload'" class="upload-section section-card">
          <h2 class="section-title">上传新插件</h2>
          <form @submit.prevent="uploadPlugin" class="upload-form">
            <div class="form-group">
              <label for="pluginName" class="form-label">插件名称</label>
              <input 
                type="text" 
                id="pluginName" 
                v-model="pluginForm.name" 
                required
                class="form-input"
                placeholder="请输入插件名称"
              >
            </div>
            
            <div class="form-group">
              <label for="pluginFile" class="form-label">选择插件文件</label>
              <div class="file-upload-wrapper">
                <label class="file-upload-label" for="pluginFile">
                  <span class="icon">📁</span>
                  选择ZIP文件
                </label>
                <input 
                  type="file" 
                  id="pluginFile" 
                  @change="handleFileUpload" 
                  accept=".zip" 
                  required
                  class="file-input"
                >
                <span class="file-name" v-if="selectedFile">{{ selectedFile.name }}</span>
              </div>
              <p class="form-help">请选择ZIP格式的插件文件</p>
            </div>
            
            <div class="form-group">
              <label for="pluginDescription" class="form-label">插件描述</label>
              <textarea 
                id="pluginDescription" 
                v-model="pluginForm.description"
                class="form-textarea"
                placeholder="请输入插件描述（可选）"
                rows="4"
              ></textarea>
            </div>
            
            <div class="form-actions">
              <button 
                type="submit" 
                :disabled="uploading"
                class="btn btn-primary"
              >
                <span v-if="uploading" class="spinner"></span>
                {{ uploading ? '上传中...' : '上传插件' }}
              </button>
              <button 
                type="button" 
                @click="resetUploadForm"
                class="btn btn-secondary"
              >
                重置
              </button>
            </div>
          </form>
          
          <div v-if="uploadMessage" class="message" :class="`message-${uploadMessage.type}`">
            {{ uploadMessage.text }}
          </div>
        </div>
        
        <!-- 更新插件弹窗 -->
        <div v-if="showUpdateModal" class="modal-overlay">
          <div class="modal-content">
            <div class="modal-header">
              <h2>更新插件</h2>
              <button @click="closeUpdateModal" class="modal-close">&times;</button>
            </div>
            <div class="modal-body">
              <form @submit.prevent="updatePlugin">
                <div class="form-group">
                  <label for="updatePluginName" class="form-label">插件名称</label>
                  <input 
                    type="text" 
                    id="updatePluginName" 
                    v-model="updateForm.name" 
                    required
                    class="form-input"
                  >
                </div>
                
                <div class="form-group">
                  <label for="updatePluginFile" class="form-label">选择插件文件</label>
                  <div class="file-upload-wrapper">
                    <label class="file-upload-label" for="updatePluginFile">
                      <span class="icon">📁</span>
                      选择ZIP文件
                    </label>
                    <input 
                      type="file" 
                      id="updatePluginFile" 
                      @change="handleUpdateFileUpload" 
                      accept=".zip" 
                      required
                      class="file-input"
                    >
                    <span class="file-name" v-if="selectedUpdateFile">{{ selectedUpdateFile.name }}</span>
                  </div>
                  <p class="form-help">请选择ZIP格式的插件文件</p>
                </div>
                
                <div class="form-group">
                  <label for="updatePluginDescription" class="form-label">插件描述</label>
                  <textarea 
                    id="updatePluginDescription" 
                    v-model="updateForm.description"
                    class="form-textarea"
                    rows="4"
                  ></textarea>
                </div>
                
                <div class="form-actions modal-actions">
                  <button 
                    type="button" 
                    @click="closeUpdateModal" 
                    class="btn btn-secondary"
                  >
                    取消
                  </button>
                  <button 
                    type="submit" 
                    :disabled="updating" 
                    class="btn btn-primary"
                  >
                    <span v-if="updating" class="spinner"></span>
                    {{ updating ? '更新中...' : '更新插件' }}
                  </button>
                </div>
              </form>
            </div>
          </div>
        </div>
        
        <!-- 插件列表 -->
        <div v-if="currentView === 'list'" class="plugins-section">
          <h2 class="section-title">可用插件</h2>
          <div v-if="loading" class="loading-container">
            <div class="spinner-large"></div>
            <p>加载中...</p>
          </div>
          <div v-else-if="filteredPlugins.length === 0" class="no-plugins">
            <div class="empty-state">
              <span class="empty-icon">📦</span>
              <h3>暂无插件</h3>
              <p v-if="searchQuery">没有找到与 "{{ searchQuery }}" 相关的插件</p>
              <p v-else>当前没有可用的插件，请先上传插件。</p>
              <button @click="showUploadForm" class="btn btn-primary">
                上传插件
              </button>
            </div>
          </div>
          <div v-else class="plugins-grid">
            <div 
              v-for="plugin in filteredPlugins" 
              :key="plugin.id" 
              class="plugin-card"
            >
              <div class="plugin-card-header">
                <h3 class="plugin-title">{{ plugin.name }}</h3>
                <span class="plugin-version">v{{ plugin.version || '1.0.0' }}</span>
              </div>
              <p class="plugin-description">{{ plugin.description || '无描述' }}</p>
              <div class="plugin-meta">
                <span class="plugin-date">{{ plugin.uploadDate }}</span>
              </div>
              <div class="plugin-actions">
                <button 
                  @click.stop="launchPlugin(plugin)" 
                  class="btn btn-success btn-small"
                >
                  启动
                </button>
                <button 
                  @click.stop="showUpdatePlugin(plugin)" 
                  class="btn btn-warning btn-small"
                >
                  更新
                </button>
                <button 
                  @click.stop="deletePlugin(plugin)" 
                  class="btn btn-danger btn-small"
                >
                  删除
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, onUnmounted  } from 'vue';
import { QWebChannel } from 'qwebchannel';

const currentView = ref('list');
const pluginForm = ref({
  name: '',
  description: ''
});
const updateForm = ref({
  id: null,
  name: '',
  description: ''
});
const selectedFile = ref(null);
const selectedUpdateFile = ref(null);
const uploading = ref(false);
const updating = ref(false);
const uploadMessage = ref(null);
const showUpdateModal = ref(false);
const plugins = ref([]);
const loading = ref(false);
const searchQuery = ref('');

let entrance = null;
let webChannel = null;

// 计算属性：过滤插件列表
const filteredPlugins = computed(() => {
  if (!searchQuery.value) {
    return plugins.value;
  }
  
  const query = searchQuery.value.toLowerCase();
  return plugins.value.filter(plugin => 
    plugin.name.toLowerCase().includes(query) || 
    (plugin.description && plugin.description.toLowerCase().includes(query))
  );
});

// 初始化QWebChannel
onMounted(() => {
  if (window.qt && window.qt.webChannelTransport) {
    new QWebChannel(window.qt.webChannelTransport, function (channel) {
      entrance = channel.objects.entrance;
      
      // 通知Python端前端已经准备就绪
      setTimeout(() => {
        if (entrance && entrance.on_frontend_ready) {
          entrance.on_frontend_ready();
        }
        loadPlugins();
      }, 100);
    });
  }
});

// 组件卸载时清理
onUnmounted(() => {
  // 清理webChannel中的回调函数
  if (webChannel) {
    // 清空所有未完成的回调
    webChannel.execCallbacks = {};
  }
});

const clearSearch = () => {
  searchQuery.value = '';
};

const resetUploadForm = () => {
  pluginForm.value = { name: '', description: '' };
  selectedFile.value = null;
  // 重置文件输入
  if (document.getElementById('pluginFile')) {
    document.getElementById('pluginFile').value = '';
  }
};

const showUpdatePlugin = (plugin) => {
  updateForm.value.id = plugin.id;
  updateForm.value.name = plugin.name;
  updateForm.value.description = plugin.description;
  showUpdateModal.value = true;
};

const closeUpdateModal = () => {
  showUpdateModal.value = false;
  updateForm.value = { id: null, name: '', description: '' };
  selectedUpdateFile.value = null;
  // 重置文件输入
  if (document.getElementById('updatePluginFile')) {
    document.getElementById('updatePluginFile').value = '';
  }
};

const deletePlugin = async (plugin) => {
  if (confirm(`确定要删除插件 "${plugin.name}" 吗？此操作不可撤销。`)) {
    if (entrance && entrance.delete_plugin) {
      entrance.delete_plugin(plugin.id, (result) => {
        try {
          // 检查result是否为空或无效
          if (!result) {
            showMessage('删除插件失败: 服务器返回空结果', 'error');
            return;
          }
          
          // 解析从Python返回的JSON字符串
          const parsedResult = JSON.parse(result);
          if (parsedResult.success) {
            // 从列表中移除已删除的插件
            plugins.value = plugins.value.filter(p => p.id !== plugin.id);
            showMessage('插件删除成功', 'success');
          } else {
            showMessage(`删除插件失败: ${parsedResult.error}`, 'error');
          }
        } catch (parseError) {
          console.error('解析Python返回结果失败:', parseError);
          console.error('原始返回结果:', result);
          showMessage(`删除插件失败: 返回结果解析错误`, 'error');
        }
      });
    } else {
      // 模拟删除插件
      plugins.value = plugins.value.filter(p => p.id !== plugin.id);
      showMessage('插件删除成功', 'success');
    }
  }
};

const handleFileUpload = (event) => {
  selectedFile.value = event.target.files[0];
};

const handleUpdateFileUpload = (event) => {
  selectedUpdateFile.value = event.target.files[0];
};

const uploadPlugin = async () => {
  if (!pluginForm.value.name || !selectedFile.value) {
    showMessage('请填写插件名称并选择文件', 'error');
    return;
  }
  
  uploading.value = true;
  
  try {
    if (entrance && entrance.upload_plugin) {
      // 读取文件内容并转换为base64
      const reader = new FileReader();
      reader.onload = function(e) {
        const fileContent = e.target.result.split(',')[1]; // 去掉data:application/zip;base64,前缀
        // 调用Python端的上传函数，传递文件名、描述和文件内容
        entrance.upload_plugin(
          pluginForm.value.name,
          pluginForm.value.description,
          fileContent,
          (result) => {
            try {
              // 检查result是否为空或无效
              if (!result) {
                showMessage('上传失败: 服务器返回空结果', 'error');
                uploading.value = false;
                return;
              }
              
              // 解析从Python返回的JSON字符串
              const parsedResult = JSON.parse(result);
              if (parsedResult.success) {
                showMessage('插件上传成功', 'success');
                resetUploadForm();
                // 切换到插件列表视图
                currentView.value = 'list';
                loadPlugins();
              } else {
                console.error(parsedResult);
                showMessage(`上传失败: ${parsedResult.error}`, 'error');
              }
            } catch (parseError) {
              console.error('解析Python返回结果失败:', parseError);
              console.error('原始返回结果:', result);
              showMessage(`上传失败: 返回结果解析错误`, 'error');
            }
            uploading.value = false;
          }
        );
      };
      reader.onerror = function() {
        showMessage('文件读取失败', 'error');
        uploading.value = false;
      };
      reader.readAsDataURL(selectedFile.value);
    } else {
      // 模拟上传成功
      showMessage('插件上传成功', 'success');
      resetUploadForm();
      uploading.value = false;
      // 切换到插件列表视图
      currentView.value = 'list';
    }
  } catch (error) {
    showMessage(`上传失败: ${error.message}`, 'error');
    uploading.value = false;
  }
};

const updatePlugin = async () => {
  if (!updateForm.value.name || !selectedUpdateFile.value) {
    showMessage('请填写插件名称并选择文件', 'error');
    return;
  }
  
  updating.value = true;
  
  try {
    if (entrance && entrance.update_plugin) {
      // 读取文件内容并转换为base64
      const reader = new FileReader();
      reader.onload = function(e) {
        const fileContent = e.target.result.split(',')[1]; // 去掉data:application/zip;base64,前缀
        // 调用Python端的更新函数，传递插件ID、文件名、描述和文件内容
        entrance.update_plugin(
          updateForm.value.id,
          updateForm.value.name,
          updateForm.value.description,
          fileContent,
          (result) => {
            try {
              // 检查result是否为空或无效
              if (!result) {
                showMessage('更新失败: 服务器返回空结果', 'error');
                updating.value = false;
                return;
              }
              
              // 解析从Python返回的JSON字符串
              const parsedResult = JSON.parse(result);
              if (parsedResult.success) {
                showMessage('插件更新成功', 'success');
                // 更新插件列表中的插件信息
                const index = plugins.value.findIndex(p => p.id === updateForm.value.id);
                if (index !== -1) {
                  plugins.value[index].name = updateForm.value.name;
                  plugins.value[index].description = updateForm.value.description;
                }
                closeUpdateModal();
              } else {
                console.error(parsedResult);
                showMessage(`更新失败: ${parsedResult.error}`, 'error');
              }
            } catch (parseError) {
              console.error('解析Python返回结果失败:', parseError);
              console.error('原始返回结果:', result);
              showMessage(`更新失败: 返回结果解析错误`, 'error');
            }
            updating.value = false;
          }
        );
      };
      reader.onerror = function() {
        showMessage('文件读取失败', 'error');
        updating.value = false;
      };
      reader.readAsDataURL(selectedUpdateFile.value);
    } else {
      // 模拟更新成功
      showMessage('插件更新成功', 'success');
      // 更新插件列表中的插件信息
      const index = plugins.value.findIndex(p => p.id === updateForm.value.id);
      if (index !== -1) {
        plugins.value[index].name = updateForm.value.name;
        plugins.value[index].description = updateForm.value.description;
      }
      closeUpdateModal();
      updating.value = false;
    }
  } catch (error) {
    showMessage(`更新失败: ${error.message}`, 'error');
    updating.value = false;
  }
};

const showMessage = (text, type) => {
  uploadMessage.value = { text, type };
  setTimeout(() => {
    uploadMessage.value = null;
  }, 3000);
};

const showUploadForm = () => {
  currentView.value = 'upload';
  uploadMessage.value = null;
};

const listPlugins = () => {
  currentView.value = 'list';
  uploadMessage.value = null;
  loadPlugins();
};

const loadPlugins = async () => {
  loading.value = true;
  
  try {
    if (entrance && entrance.list_plugins) {
      entrance.list_plugins((result) => {
        try {
          // 检查result是否为空或无效
          if (!result) {
            showMessage('加载插件列表失败: 服务器返回空结果', 'error');
            loading.value = false;
            return;
          }
          
          // 解析从Python返回的JSON字符串
          const parsedResult = JSON.parse(result);
          if (parsedResult.success) {
            plugins.value = parsedResult.plugins;
          } else {
            showMessage(`加载插件列表失败: ${parsedResult.error}`, 'error');
          }
        } catch (parseError) {
          console.error('解析Python返回结果失败:', parseError);
          console.error('原始返回结果:', result);
          showMessage(`加载插件列表失败: 返回结果解析错误`, 'error');
        }
        loading.value = false;
      });
    } else {
      // 模拟插件数据
      plugins.value = [
        { id: 1, name: '示例插件1', description: '这是一个示例插件，用于演示插件功能', version: '1.0.0', uploadDate: '2023-01-01' },
        { id: 2, name: '示例插件2', description: '另一个示例插件，展示不同的功能', version: '2.1.0', uploadDate: '2023-02-15' },
        { id: 3, name: '数据分析插件', description: '用于数据分析和可视化展示的强大工具', version: '1.5.2', uploadDate: '2023-03-22' }
      ];
      loading.value = false;
    }
  } catch (error) {
    showMessage(`加载插件列表失败: ${error.message}`, 'error');
    loading.value = false;
  }
};

const launchPlugin = (plugin) => {
  if (entrance && entrance.launch_plugin) {
    entrance.launch_plugin(plugin.id, (result) => {
      try {
        // 检查result是否为空或无效
        if (!result) {
          showMessage('启动插件失败: 服务器返回空结果', 'error');
          return;
        }
        // 解析从Python返回的JSON字符串
        const parsedResult = JSON.parse(result);
        if (parsedResult.success) {
          console.log(parsedResult.url)
        } else {
          showMessage(`启动插件失败: ${parsedResult.error}`, 'error');
        }
      } catch (parseError) {
        console.error('解析Python返回结果失败:', parseError);
        console.error('原始返回结果:', result);
        showMessage(`启动插件失败: 返回结果解析错误`, 'error');
      }
    });
  } else {
    // 模拟启动插件
    console.log(`启动插件: ${plugin.name}`);
    showMessage(`插件 "${plugin.name}" 已启动`, 'success');
  }
};
</script>

<style lang="scss">
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

#app {
  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
  background-color: #f5f7fa;
  min-height: 100vh;
  color: #333;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

.app-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30px;
  padding-bottom: 20px;
  border-bottom: 1px solid #e1e5eb;
}

.app-title {
  font-size: 2rem;
  font-weight: 600;
  color: #2c3e50;
}

.header-actions {
  display: flex;
  align-items: center;
}

.search-box {
  position: relative;
  display: flex;
  align-items: center;
}

.search-input {
  padding: 10px 40px 10px 15px;
  border: 1px solid #d1d8e0;
  border-radius: 20px;
  font-size: 14px;
  width: 250px;
  transition: all 0.3s ease;
  
  &:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
  }
}

.clear-button {
  position: absolute;
  right: 10px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 16px;
  color: #95a5a6;
  
  &:hover {
    color: #e74c3c;
  }
}

.navbar {
  display: flex;
  gap: 10px;
  margin-bottom: 30px;
  background: white;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}

.nav-button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 20px;
  border: none;
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
  font-size: 16px;
  font-weight: 500;
  transition: all 0.3s ease;
  
  &:hover {
    background-color: #f1f5f9;
  }
  
  &.active {
    background-color: #3498db;
    color: white;
  }
}

.section-card {
  background: white;
  border-radius: 10px;
  padding: 25px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  margin-bottom: 30px;
}

.section-title {
  font-size: 1.5rem;
  margin-bottom: 20px;
  color: #2c3e50;
  font-weight: 600;
}

.form-group {
  margin-bottom: 20px;
}

.form-label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
  color: #34495e;
}

.form-input,
.form-textarea {
  width: 100%;
  padding: 12px 15px;
  border: 1px solid #d1d8e0;
  border-radius: 6px;
  font-size: 15px;
  font-family: inherit;
  transition: border-color 0.3s ease;
  
  &:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
  }
}

.form-textarea {
  resize: vertical;
  min-height: 100px;
}

.form-help {
  margin-top: 5px;
  font-size: 13px;
  color: #7f8c8d;
}

.file-upload-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  gap: 15px;
  flex-wrap: wrap;
}

.file-upload-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 15px;
  background-color: #3498db;
  color: white;
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 0.3s ease;
  
  &:hover {
    background-color: #2980b9;
  }
}

.file-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.file-name {
  font-size: 14px;
  color: #7f8c8d;
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.form-actions {
  display: flex;
  gap: 15px;
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid #ecf0f1;
}

.btn {
  padding: 12px 25px;
  border: none;
  border-radius: 6px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  
  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
}

.btn-primary {
  background-color: #3498db;
  color: white;
  
  &:hover:not(:disabled) {
    background-color: #2980b9;
  }
}

.btn-secondary {
  background-color: #95a5a6;
  color: white;
  
  &:hover:not(:disabled) {
    background-color: #7f8c8d;
  }
}

.btn-success {
  background-color: #2ecc71;
  color: white;
  
  &:hover:not(:disabled) {
    background-color: #27ae60;
  }
}

.btn-warning {
  background-color: #f39c12;
  color: white;
  
  &:hover:not(:disabled) {
    background-color: #d35400;
  }
}

.btn-danger {
  background-color: #e74c3c;
  color: white;
  
  &:hover:not(:disabled) {
    background-color: #c0392b;
  }
}

.btn-small {
  padding: 8px 15px;
  font-size: 14px;
}

.message {
  padding: 15px;
  border-radius: 6px;
  margin-top: 20px;
  font-weight: 500;
}

.message-success {
  background-color: #d5f5e3;
  color: #27ae60;
  border: 1px solid #2ecc71;
}

.message-error {
  background-color: #fadbd8;
  color: #c0392b;
  border: 1px solid #e74c3c;
}

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-content {
  background: white;
  border-radius: 10px;
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 25px;
  border-bottom: 1px solid #ecf0f1;
}

.modal-header h2 {
  margin: 0;
  font-size: 1.5rem;
  color: #2c3e50;
}

.modal-close {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #95a5a6;
  padding: 0;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  
  &:hover {
    background-color: #ecf0f1;
    color: #e74c3c;
  }
}

.modal-body {
  padding: 25px;
}

.modal-actions {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #ecf0f1;
}

.plugins-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 25px;
}

.plugin-card {
  background: white;
  border-radius: 10px;
  padding: 20px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  border: 1px solid #e1e5eb;
  
  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
  }
}

.plugin-card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 15px;
}

.plugin-title {
  font-size: 1.2rem;
  font-weight: 600;
  color: #2c3e50;
  margin: 0;
}

.plugin-version {
  background-color: #3498db;
  color: white;
  padding: 3px 8px;
  border-radius: 12px;
  font-size: 0.8rem;
  font-weight: 500;
}

.plugin-description {
  color: #7f8c8d;
  margin-bottom: 20px;
  line-height: 1.5;
}

.plugin-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-top: 15px;
  border-top: 1px dashed #ecf0f1;
}

.plugin-date {
  font-size: 0.85rem;
  color: #95a5a6;
}

.plugin-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 50px 0;
  color: #7f8c8d;
}

.spinner {
  width: 20px;
  height: 20px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  border-top-color: white;
  animation: spin 1s linear infinite;
}

.spinner-large {
  width: 40px;
  height: 40px;
  border: 4px solid #ecf0f1;
  border-radius: 50%;
  border-top-color: #3498db;
  animation: spin 1s linear infinite;
  margin-bottom: 20px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.empty-state {
  text-align: center;
  padding: 50px 20px;
}

.empty-icon {
  font-size: 4rem;
  margin-bottom: 20px;
  display: block;
}

.empty-state h3 {
  margin-bottom: 10px;
  color: #2c3e50;
}

.empty-state p {
  color: #7f8c8d;
  margin-bottom: 25px;
}

@media (max-width: 768px) {
  .container {
    padding: 15px;
  }
  
  .app-header {
    flex-direction: column;
    gap: 15px;
    align-items: flex-start;
  }
  
  .header-actions {
    width: 100%;
  }
  
  .search-input {
    width: 100%;
  }
  
  .navbar {
    flex-direction: column;
  }
  
  .plugins-grid {
    grid-template-columns: 1fr;
  }
  
  .form-actions {
    flex-direction: column;
  }
  
  .modal-content {
    width: 95%;
    margin: 10px;
  }
  
}
// ... existing code ...
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* 滚动条样式 */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: #f1f5f9;
  border-radius: 4px;
}

::-webkit-scrollbar-thumb {
  background: #cfd8e3;
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: #95a5a6;
}

::-webkit-scrollbar-corner {
  background: transparent;
}

.empty-state {
  text-align: center;
  padding: 50px 20px;
}
// ... existing code ...
</style>